// Copyright (C) 2012 Volkov Zachar
// This file is part of the "zAxis Game Engine".
// For conditions of distribution and use, see copyright notice zx_Common.hpp

#include "zx_Video.hpp"
#include "gl/glew.h"

namespace zx {

//----------------------------------------------------------------------------//
//----------------------------------------------------------------------------//
RenderTarget::RenderTarget( uint _width, uint _height, FrameBuffers _create_render_buffers ) : m_color_texture_layer( 0 )
                                                                                              , m_depth_texture_layer( 0 )
                                                                                              , m_stencil_texture_layer( 0 )
                                                                                              , m_width( _width )
                                                                                              , m_height( _height )
                                                                                              , m_valid( true )
                                                                                              , m_handle( 0 )
{
     // create frame buffer object
     glGenFramebuffersEXT( 1, &m_handle );
     glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_handle );

     // create render buffers
     if( _create_render_buffers & FBT_COLOR )
     {
          SetColorBuffer( new RenderBuffer( PF_RGBA8, m_width, m_height ) );
     }

     if( _create_render_buffers & FBT_DEPTH_STENCIL )
     {
          RenderBufferPtr _dsb = new RenderBuffer( PF_D24S8, m_width, m_height );
         
          if( _create_render_buffers & FBT_DEPTH )
          {
               SetDepthBuffer( _dsb );
          }

          if( _create_render_buffers & FBT_STENCIL )
          {
               SetStencilBuffer( _dsb );
          }
     }
}
//----------------------------------------------------------------------------//
RenderTarget::~RenderTarget( void )
{
     if( m_handle )
     {
          SetColorBuffer( 0 );
          SetDepthBuffer( 0 );
          SetStencilBuffer( 0 );
          SetColorTexture( 0 );
          SetDepthTexture( 0 );
          SetStencilTexture( 0 );
          glDeleteFramebuffersEXT( 1, &m_handle );   
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::SetColorBuffer( RenderBufferPtr _buffer )
{
     if( m_color_buffer != _buffer )
     {
          _Bind();
          
          uint _buff_id = zxChoose( _buffer, _buffer -> m_handle, 0 );
          glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, _buff_id );
          
          m_color_buffer = _buffer;
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::SetDepthBuffer( RenderBufferPtr _buffer )
{
     if( m_depth_buffer != _buffer )
     {
          _Bind();
          
          uint _buff_id = zxChoose( _buffer, _buffer -> m_handle, 0 );
          glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, _buff_id );
          
          m_depth_buffer = _buffer;
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::SetStencilBuffer( RenderBufferPtr _buffer )
{
     if( m_stencil_buffer != _buffer )
     {
          _Bind();
          
          uint _buff_id = zxChoose( _buffer, _buffer -> m_handle, 0 );
          glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, _buff_id );
          
          m_stencil_buffer = _buffer;
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::SetColorTexture( TexturePtr _texture, uint _z )
{
     if( m_color_texture != _texture )
     {
          _Bind();
          
          if( _texture )
          {
               if( _texture -> m_type == TT_1D )
               {
                    glFramebufferTexture1DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_1D, _texture -> m_handle, 0 );
               }
               else if( _texture -> m_type == TT_3D )
               {
                    glFramebufferTexture3DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_3D, _texture -> m_handle, 0, _z );
               }
               else // 2d, rect, cubemap
               {
                    glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, VideoDriver::_glTextureType( _texture -> m_type, _z ), _texture -> m_handle, 0 );
               }
          }
          else
          {
               glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0 );
          }


          if( m_color_texture )
          {
               m_color_texture -> GenerateMipmap();
          }

          m_color_texture = _texture;
          m_color_texture_layer = _z;
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::SetDepthTexture( TexturePtr _texture, uint _z )
{
     if( m_depth_texture != _texture )
     {
          _Bind();

          if( _texture )
          {
               if( _texture -> m_type == TT_1D )
               {
                    glFramebufferTexture1DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_1D, _texture -> m_handle, 0 );
               }
               else if( _texture -> m_type == TT_3D )
               {
                    glFramebufferTexture3DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_3D, _texture -> m_handle, 0, _z );
               }
               else // 2d, rect, cubemap
               {
                    glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, VideoDriver::_glTextureType( _texture -> m_type, _z ), _texture -> m_handle, 0 );
               }
          }
          else
          {
               glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0 );
          }

          if( m_depth_texture )
          {
               m_depth_texture -> GenerateMipmap();
          }

          m_depth_texture = _texture;
          m_depth_texture_layer = _z;
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::SetStencilTexture( TexturePtr _texture, uint _z )
{
      if( m_stencil_texture != _texture )
     {
          _Bind();

          if( _texture )
          {
               if( _texture -> m_type == TT_1D )
               {
                    glFramebufferTexture1DEXT( GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_1D, _texture -> m_handle, 0 );
               }
               else if( _texture -> m_type == TT_3D )
               {
                    glFramebufferTexture3DEXT( GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_3D, _texture -> m_handle, 0, _z );
               }
               else // 2d, rect, cubemap
               {
                    glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, VideoDriver::_glTextureType( _texture -> m_type, _z ), _texture -> m_handle, 0 );
               }
          }
          else
          {
               glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0 );
          }

          if( m_stencil_texture )
          {
               m_stencil_texture -> GenerateMipmap();
          }

          m_stencil_texture = _texture;
          m_stencil_texture_layer = _z;
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::_Validate( void )
{
     _Bind();
     GLenum _status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
     if( _status == GL_FRAMEBUFFER_COMPLETE_EXT )
     {
          m_valid = true;
          m_error.clear();
     }
     else
     {
          m_valid = false;

          switch( _status )
          {
               case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: m_error = "RenderTarget error: Framebuffer incomplete attachment"; break;
               case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: m_error = "RenderTarget error: Framebuffer incomplete, missing attachment"; break;
               case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: m_error = "RenderTarget error: Framebuffer incomplete, attached images must have same dimensions"; break;
               case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: m_error = "RenderTarget error: Framebuffer incomplete, attached images must have same format"; break;
               case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: m_error = "RenderTarget error: Framebuffer incomplete, missing draw buffer"; break;
               case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: m_error = "RenderTarget error: Framebuffer incomplete, missing read buffer"; break;
               case GL_FRAMEBUFFER_UNSUPPORTED_EXT: m_error = "RenderTarget error: Unsupported framebuffer format"; break;
               case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: m_error = "RenderTarget error: Framebuffer incomplete multisample"; break;
               default : m_error = Format( "RenderTarget error: Unknown error 0x%04x", _status ); break;
          }
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::_Bind( void )
{
     GLint _current_buffer = 0;
     glGetIntegerv( GL_FRAMEBUFFER_BINDING_EXT, &_current_buffer );

     if( _current_buffer != m_handle )
     {
          glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_handle );    
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::_Unbind( void )
{
     GLint _current_buffer = 0;
     glGetIntegerv( GL_FRAMEBUFFER_BINDING_EXT, &_current_buffer );

     if( _current_buffer != m_handle )
     {
          glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );    
     }
}
//----------------------------------------------------------------------------//
void RenderTarget::_Update( void )
{    /*
     // tmp
     uint _tmp;
     glGenFramebuffersEXT( 1, &_tmp );
     glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, _tmp );
                    
     glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, VideoDriver::_glTextureType( m_color_texture -> m_type, 0 ), m_color_texture -> m_handle, 0 );
     glBindFramebufferEXT( GL_READ_FRAMEBUFFER_EXT, m_handle );
     glBindFramebufferEXT( GL_DRAW_FRAMEBUFFER_EXT, _tmp);
     glBlitFramebufferEXT( 0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_LINEAR );
     glDeleteFramebuffersEXT( 1, &_tmp );

     _Bind();*/
     if( m_color_texture ) { m_color_texture -> GenerateMipmap(); }
     if( m_depth_texture ) { m_depth_texture -> GenerateMipmap();}
     if( m_stencil_texture ) { m_stencil_texture -> GenerateMipmap(); }
  
}
//----------------------------------------------------------------------------//
//----------------------------------------------------------------------------//

} // namespace zx
